<script lang="ts">
import { Teleport, h, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { createPopper } from '@popperjs/core'
import utils from '@/common/utils'

interface PropsType {
    scrollbarWidth: number
    scrollbarDraggable: boolean
    scrollbarOpacity: number
    scrollbarColor: string
}
interface StateType {
    popShown: boolean
    prepareToHide: boolean
    mouseDown: boolean
    mouseIn: boolean
    startScrollLeft?: number
    startMouseX?: number
    scacle: number
    popInstance: any
}
export default {
    props: {
        scrollbarWidth: {
            type: Number,
            default: 8,
        },
        scrollbarDraggable: {
            type: Boolean,
            default: true,
        },
        scrollbarOpacity: {
            type: Number,
            default: 0.5,
        },
        scrollbarColor: {
            type: String,
            default: '#666',
        },
    },
    setup(props: PropsType, ctx) {
        const state = reactive<StateType>({
            popShown: false,
            prepareToHide: false,
            mouseDown: false,
            mouseIn: false,
            startScrollLeft: undefined,
            startMouseX: undefined,
            scacle: 1,
            popInstance: undefined,
        })

        watch(() => state.popShown, () => {
            if (state.popShown) {
                methods.onPopShown()
            } else {
                methods.onPopHidden()
            }
        })

        onMounted(() => {
            methods.addEventListener()
        })

        const targetRef = ref()
        const popRef = ref()
        const methods = {
            getTargetEl() {
                return utils.getEl(targetRef.value)
            },
            getPopEl() {
                return utils.getEl(popRef.value)
            },
            addEventListener() {
                let targetEl = methods.getTargetEl()!
                targetEl.addEventListener('scroll', methods.onScroll)
                targetEl.addEventListener('mouseover', methods.onMouseover)
                targetEl.addEventListener('mouseleave', methods.onMouseout)
                targetEl.addEventListener('click', methods.onClick)
            },
            addPopEventListener() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }
                popEl.addEventListener('mousedown', methods.onMousedown)
                methods.addDocumentEventListener()
                popEl.addEventListener('mouseover', methods.onMouseover)
                popEl.addEventListener('mouseout', methods.onMouseout)
            },
            addDocumentEventListener() {
                window.document.addEventListener('mousemove', methods.onMousemove)
                window.document.addEventListener('mouseup', methods.onMouseup)
            },
            removeDocumentEventListener() {
                window.document.removeEventListener('mousemove', methods.onMousemove)
                window.document.removeEventListener('mouseup', methods.onMouseup)
            },
            onScroll() {
                methods.pinToTarget()
            },
            onMousedown(e: MouseEvent) {
                e.preventDefault()
                state.mouseDown = true
                let targetEl = methods.getTargetEl()!
                state.startScrollLeft = targetEl.scrollLeft
                state.startMouseX = e.clientX
            },
            onMousemove(e: MouseEvent) {
                if (state.mouseDown) {
                    e.preventDefault()
                    let targetEl = methods.getTargetEl()!
                    let newScrollLeft = (e.clientX - state.startMouseX!) / state.scacle + state.startScrollLeft!
                    targetEl.scroll(newScrollLeft, 0,)
                }
            },
            onMouseup() {
                state.mouseDown = false
                if (state.mouseIn) {
                    return
                }
                methods.readyToHide()
            },
            onMouseover() {
                state.popShown = true
                state.prepareToHide = false
                state.mouseIn = true
            },
            onMouseout() {
                state.mouseIn = false
                if (state.mouseDown) {
                    return
                }
                methods.readyToHide()
            },
            readyToHide() {
                state.prepareToHide = true
                setTimeout(() => {
                    if (state.prepareToHide) {
                        state.popShown = false
                    }
                }, 50)
            },
            onClick() {
                let targetEl = methods.getTargetEl()
                if (targetEl === undefined) {
                    return
                }
                if (targetEl.clientWidth === targetEl.scrollWidth) {
                    state.popShown = false
                } else {
                    state.popShown = true
                    state.prepareToHide = false
                    methods.pinToTarget()
                }
            },
            onPopShown() {
                nextTick(() => {
                    methods.pinToTarget()
                    if (props.scrollbarDraggable) {
                        methods.addPopEventListener()
                    }
                })
            },
            onPopHidden() {
                if (props.scrollbarDraggable) {
                    methods.removeDocumentEventListener()
                }
            },
            pinToTarget() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }

                let targetEl = methods.getTargetEl()!

                state.popInstance?.destroy()
                state.popInstance = createPopper(targetEl, popEl, {
                    strategy: 'fixed',
                    placement: 'bottom-start',
                    modifiers: [
                        {
                            name: 'updatePopWidth',
                            enabled: true,
                            phase: 'main',
                            fn: (opts) => {
                                let { reference, popper } = opts.state.elements
                                const referenceEl = reference as HTMLElement
                                let scacle = referenceEl.clientWidth / referenceEl.scrollWidth
                                if (scacle >= 0.99 && scacle <= 1.01) {
                                    state.popShown = false
                                } else {
                                    let popWidth = referenceEl.clientWidth * scacle
                                    popper.style.width = popWidth + 'px'
                                    let offsetX = referenceEl.scrollLeft * scacle
                                    opts.state.options.modifiers.find(a => a.name === 'offset').options.offset[0] = offsetX
                                }
                                state.scacle = scacle
                            }
                        },
                        {
                            name: 'offset',
                            options: {
                                offset: [0, -props.scrollbarWidth]
                            },
                            requires: ['updatePopWidth']
                        }
                    ]
                })
            },
        }
        return () => {
            let targetAttrs = Object.assign({
                ref: targetRef,
                class: 'c-scroll-hide'
            }, ctx.attrs)
            let elList = [h(ctx.slots.default!()[0], targetAttrs)]
            if (state.popShown || state.mouseDown) {
                let popStyle = {
                    opacity: props.scrollbarOpacity,
                    width: props.scrollbarWidth + 'px',
                    borderRadius: props.scrollbarWidth / 2 + 'px',
                    backgroundColor: props.scrollbarColor,
                }

                let popRender = h('div', {
                    ref: popRef,
                    style: popStyle,
                    class: 'c-scrollbar'
                })

                elList.push(h(Teleport, {
                    to: 'body'
                }, popRender))
            }

            return elList
        }
    }
}
</script>